//****************************************************************************************
//
// Module file for the exported managed class representing Checkers Board engine interface
//
//****************************************************************************************


#include "Stdafx.h"

#include "CBEngine.h"

namespace CheckersBoardEngine {

		// constructor - inititialization
		CBEngine::CBEngine(String^ enginePath)
		{
			m_pParams = NULL;
			m_pSynch = NULL;

			// init configuration classes
			m_pParams = new CBThreadParams();
			memset(m_pParams, 0, sizeof(CBThreadParams));
			m_pParams->pSynch = new CBSynch();
			memset(m_pParams->pSynch, 0, sizeof(CBSynch));

			// init events
			InitEvents(m_pParams->pSynch);

			// load the engine			
			wchar_t* szError = new wchar_t[MAX_INIT_ERROR];

			wchar_t* szEnginePath = (wchar_t*)Marshal::StringToHGlobalUni(enginePath).ToPointer();
			m_pParams->pAPI = InitializeCBEngine(szEnginePath, szError);
			Marshal::FreeHGlobal((IntPtr)szEnginePath);

			// check error
			if(m_pParams->pAPI == NULL) 
			{
				CleanUp();

				String^ engineError = gcnew String(szError);
				delete[] szError;
				throw gcnew ApplicationException(engineError);
			}				
		
			// init worker events
			m_pSynch = new CBWorkerSynch();
			memset(m_pSynch, 0, sizeof(CBWorkerSynch));
			InitWorkerEvents();

			// create the background worker	thread
			_backgroundThread = gcnew Thread(gcnew ThreadStart(this, &CBEngine::BackgroundThreadWorker));
			_backgroundThread->Priority = System::Threading::ThreadPriority::Lowest;
			_backgroundThread->Start();
		}

		// clean up		
		void CBEngine::Terminate() 
		{
			// if necessary, abort engine
			if(getenginebusy(m_pParams->pAPI))
			{
				abortengine(m_pParams->pAPI);
			}
			
			// stop background thread
			::SetEvent(m_pSynch->hEvents[WORKER_TERMINATE]);
			::WaitForSingleObject(m_pSynch->hEvents[WORKER_THREAD_EXITED], INFINITE);

			// Free engine DLL
			if(m_pParams != NULL)
			{
				if(m_pParams->pAPI != NULL) 
				{
					DestroyCBEngine(m_pParams->pAPI);
					m_pParams->pAPI = NULL;
				}
			}

			// cleanup
			CleanUp();
		}

		// checks, if engine is computing the move
		bool CBEngine::IsEngineBusy()
		{
			return getenginebusy(m_pParams->pAPI) != 0;
		}

		// returns the progress of computing the move
		String^ CBEngine::GetEngineInfo()
		{
			// TO-DO: do we need synchronization?
			return gcnew String(m_pParams->pAPI->engineOutput);
		}

		// returns the code of supported checkers - code according to the PDN 3.0 standard
		int CBEngine::GetCheckersType()
		{
			// check engine busy
			if(getenginebusy(m_pParams->pAPI))
				throw gcnew CBEngineBusyException();

			return gametype(m_pParams->pAPI);
		}

		// returns the CB engine name
		String^ CBEngine::GetEngineName()
		{
			// check engine busy
			if(getenginebusy(m_pParams->pAPI))
				throw gcnew CBEngineBusyException();

			// check if we have function to get the engine name
			if(enginename == NULL)
				return String::Empty;
			
			// get the engine name
			const int iEngineNameLen = 256;
			char szEngineName[iEngineNameLen];
			memset(szEngineName, 0, iEngineNameLen);
			enginename(m_pParams->pAPI, szEngineName);

			// convert the return value
			return gcnew String(szEngineName);
		}

		// starts the computing of the move
		void CBEngine::ComputeMove(CBGameRecord^ rec)
		{
			// check engine busy
			if(getenginebusy(m_pParams->pAPI))
				throw gcnew CBEngineBusyException();

			// prepare engine settings
			ConvertGameRecord(m_pParams, rec);

			// run the engine
			::SetEvent(m_pSynch->hEvents[WORKER_COMPUTE_MOVE]);
		}

		// get engine options
		void CBEngine::GetEngineOptions([Out] EngineOptions^% opts, [Out] EngineMoreOptions^% optsMore)
		{
			// check engine busy
			if(getenginebusy(m_pParams->pAPI))
				throw gcnew CBEngineBusyException();

			// get engine options
			ENGINE_OPTIONS* options = new ENGINE_OPTIONS();
			MORE_ENGINE_OPTIONS* more_options = new MORE_ENGINE_OPTIONS();

			getengineoptions(m_pParams->pAPI, options);
			get_more_engine_options(m_pParams->pAPI, more_options);

			// convert models
			opts = ConvertEngineOptions(options);
			optsMore = ConvertEngineOptions(more_options);

			// tidy up
			delete options;
			delete more_options;	
		}

		// set engine options
		void CBEngine::SetEngineOptions(EngineOptions^ opts, EngineMoreOptions^ optsMore)
		{
			// check engine busy
			if(getenginebusy(m_pParams->pAPI))
				throw gcnew CBEngineBusyException();

			// convert model
			ENGINE_OPTIONS* options = ConvertEngineOptions(opts);
			MORE_ENGINE_OPTIONS* more_options = ConvertEngineOptions(optsMore);

			// set engine options
			setengineoptions(m_pParams->pAPI, options);
			set_more_engine_options(m_pParams->pAPI, more_options);

			// tidy up
			delete options;
			delete more_options;	

		}

		void CBEngine::BackgroundThreadWorker()
		{
			// start the engine thread
			::CreateThread(NULL, 0, CBEngineThread, m_pParams, 0, NULL);

			// do waiting for work
			while(true)
			{
				// wait for an event
				DWORD dwResult = ::WaitForMultipleObjects(WORKER_EVENT_COUNT, m_pSynch->hEvents, FALSE, INFINITE);

				// if some work
				if(dwResult >= WAIT_OBJECT_0 && dwResult < WAIT_OBJECT_0 + WORKER_EVENT_COUNT)
				{
					// switch work
					dwResult -= WAIT_OBJECT_0;
					switch(dwResult)
					{
					case WORKER_TERMINATE:
						::ResetEvent(m_pSynch->hEvents[WORKER_TERMINATE]);

						// check to terminate engine thread
						::SetEvent(m_pParams->pSynch->hEvents[CB_EVENT_TERMINATE]);
						::WaitForSingleObject(m_pParams->pSynch->hEvents[CB_EVENT_THREAD_EXITED], INFINITE);

						// finish
						goto Exit;				
					case WORKER_COMPUTE_MOVE:
						::ResetEvent(m_pSynch->hEvents[WORKER_COMPUTE_MOVE]);

						m_pParams->phMoveComputed = &(m_pSynch->hEvents[WORKER_MOVE_COMPUTED]);

						// run the engine
						::SetEvent(m_pParams->pSynch->hEvents[CB_EVENT_COMPUTEMOVE]);				
						break;
					case WORKER_MOVE_COMPUTED:
						{
							::ResetEvent(m_pSynch->hEvents[WORKER_MOVE_COMPUTED]);

							// create event args and convert the move
							ComputedMoveEventArgs^ args = gcnew ComputedMoveEventArgs();
							args->Move = ConvertMove(m_pParams->move);

							MoveComputed(this, args);
						} break;
					} // switch
				}
			} // while

		Exit:;
			::SetEvent(m_pSynch->hEvents[WORKER_THREAD_EXITED]);
		}


		// internal variable clean up
		void CBEngine::CleanUp() 
		{
			if(m_pSynch != NULL) 
			{
				CloseWorkerEvents();
				delete m_pSynch;
				m_pSynch = NULL;
			}

			if(m_pParams != NULL) 						
			{
				if(m_pParams->pSynch != NULL) 
				{
					CloseEvents(m_pParams->pSynch);
					delete m_pParams->pSynch;
					m_pParams->pSynch = NULL;
				}

				delete m_pParams;
				m_pParams = NULL;
			}
		}

		// initializes worker events
		void CBEngine::InitWorkerEvents()
		{
			for(int i = 0; i < WORKER_EVENT_COUNT; i++)
				m_pSynch->hEvents[i] = ::CreateEvent(NULL, TRUE, FALSE, NULL);

		}

		// closes worker events
		void CBEngine::CloseWorkerEvents()
		{
			for(int i = 0; i < WORKER_EVENT_COUNT; i++)
				::CloseHandle(m_pSynch->hEvents[i]);
		}

}
